
<!doctype html>
<html lang="zh" class="no-js">
  <head>
    
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width,initial-scale=1">
      
      
        <meta name="author" content="HENG">
      
      
      
        <link rel="prev" href="database.html">
      
      
        <link rel="next" href="cron.html">
      
      
      <link rel="icon" href="assets/images/favicon.png">
      <meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.20">
    
    
      
        <title>文件上传与生成 - Django杂记</title>
      
    
    
      <link rel="stylesheet" href="assets/stylesheets/main.66ac8b77.min.css">
      
        
        <link rel="stylesheet" href="assets/stylesheets/palette.06af60db.min.css">
      
      


    
    
      
    
    
      
        
        
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
        <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
        <style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
      
    
    
    <script>__md_scope=new URL(".",location),__md_hash=e=>[...e].reduce((e,_)=>(e<<5)-e+_.charCodeAt(0),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
    
      

    
    
    
  </head>
  
  
    
    
      
    
    
    
    
    <body dir="ltr" data-md-color-scheme="default" data-md-color-primary="deep-orange" data-md-color-accent="indigo">
  
    
    <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
    <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
    <label class="md-overlay" for="__drawer"></label>
    <div data-md-component="skip">
      
        
        <a href="#_1" class="md-skip">
          跳转至
        </a>
      
    </div>
    <div data-md-component="announce">
      
    </div>
    
    
      

  

<header class="md-header md-header--shadow" data-md-component="header">
  <nav class="md-header__inner md-grid" aria-label="页眉">
    <a href="index.html" title="Django杂记" class="md-header__button md-logo" aria-label="Django杂记" data-md-component="logo">
      
  
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54Z"/></svg>

    </a>
    <label class="md-header__button md-icon" for="__drawer">
      
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2Z"/></svg>
    </label>
    <div class="md-header__title" data-md-component="header-title">
      <div class="md-header__ellipsis">
        <div class="md-header__topic">
          <span class="md-ellipsis">
            Django杂记
          </span>
        </div>
        <div class="md-header__topic" data-md-component="header-topic">
          <span class="md-ellipsis">
            
              文件上传与生成
            
          </span>
        </div>
      </div>
    </div>
    
      
        <form class="md-header__option" data-md-component="palette">
  
    
    
    
    <input class="md-option" data-md-color-media="(prefers-color-scheme: light)" data-md-color-scheme="default" data-md-color-primary="deep-orange" data-md-color-accent="indigo"  aria-label="亮色模式"  type="radio" name="__palette" id="__palette_0">
    
      <label class="md-header__button md-icon" title="亮色模式" for="__palette_1" hidden>
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a4 4 0 0 0-4 4 4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 10a6 6 0 0 1-6-6 6 6 0 0 1 6-6 6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12 20 8.69Z"/></svg>
      </label>
    
  
    
    
    
    <input class="md-option" data-md-color-media="(prefers-color-scheme: dark)" data-md-color-scheme="slate" data-md-color-primary="indigo" data-md-color-accent="cyan"  aria-label="暗色模式"  type="radio" name="__palette" id="__palette_1">
    
      <label class="md-header__button md-icon" title="暗色模式" for="__palette_0" hidden>
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 18c-.89 0-1.74-.2-2.5-.55C11.56 16.5 13 14.42 13 12c0-2.42-1.44-4.5-3.5-5.45C10.26 6.2 11.11 6 12 6a6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12 20 8.69Z"/></svg>
      </label>
    
  
</form>
      
    
    
      <script>var media,input,key,value,palette=__md_get("__palette");if(palette&&palette.color){"(prefers-color-scheme)"===palette.color.media&&(media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']"),palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent"));for([key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
    
    
    
      <label class="md-header__button md-icon" for="__search">
        
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
      </label>
      <div class="md-search" data-md-component="search" role="dialog">
  <label class="md-search__overlay" for="__search"></label>
  <div class="md-search__inner" role="search">
    <form class="md-search__form" name="search">
      <input type="text" class="md-search__input" name="query" aria-label="搜索" placeholder="搜索" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
      <label class="md-search__icon md-icon" for="__search">
        
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
        
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12Z"/></svg>
      </label>
      <nav class="md-search__options" aria-label="查找">
        
        <button type="reset" class="md-search__icon md-icon" title="清空当前内容" aria-label="清空当前内容" tabindex="-1">
          
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"/></svg>
        </button>
      </nav>
      
    </form>
    <div class="md-search__output">
      <div class="md-search__scrollwrap" data-md-scrollfix>
        <div class="md-search-result" data-md-component="search-result">
          <div class="md-search-result__meta">
            正在初始化搜索引擎
          </div>
          <ol class="md-search-result__list" role="presentation"></ol>
        </div>
      </div>
    </div>
  </div>
</div>
    
    
      <div class="md-header__source">
        <a href="https://github.com/lmliheng/codes" title="前往仓库" class="md-source" data-md-component="source">
  <div class="md-source__icon md-icon">
    
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!--! Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2024 Fonticons, Inc.--><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
  </div>
  <div class="md-source__repository">
    Heng's Codes
  </div>
</a>
      </div>
    
  </nav>
  
</header>
    
    <div class="md-container" data-md-component="container">
      
      
        
          
        
      
      <main class="md-main" data-md-component="main">
        <div class="md-main__inner md-grid">
          
            
              
              <div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
                <div class="md-sidebar__scrollwrap">
                  <div class="md-sidebar__inner">
                    



<nav class="md-nav md-nav--primary" aria-label="导航栏" data-md-level="0">
  <label class="md-nav__title" for="__drawer">
    <a href="index.html" title="Django杂记" class="md-nav__button md-logo" aria-label="Django杂记" data-md-component="logo">
      
  
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54Z"/></svg>

    </a>
    Django杂记
  </label>
  
    <div class="md-nav__source">
      <a href="https://github.com/lmliheng/codes" title="前往仓库" class="md-source" data-md-component="source">
  <div class="md-source__icon md-icon">
    
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!--! Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2024 Fonticons, Inc.--><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
  </div>
  <div class="md-source__repository">
    Heng's Codes
  </div>
</a>
    </div>
  
  <ul class="md-nav__list" data-md-scrollfix>
    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="index.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    主页
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="settings.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    配置文件
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="models.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    模型
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="urls.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    路由
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="view.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    视图
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="admin.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    管理员
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="template.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    模板
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="static.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    静态资源
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="database.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    数据库
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
    
  
  
  
    <li class="md-nav__item md-nav__item--active">
      
      <input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
      
      
      
        <label class="md-nav__link md-nav__link--active" for="__toc">
          
  
  <span class="md-ellipsis">
    文件上传与生成
  </span>
  

          <span class="md-nav__icon md-icon"></span>
        </label>
      
      <a href="file.html" class="md-nav__link md-nav__link--active">
        
  
  <span class="md-ellipsis">
    文件上传与生成
  </span>
  

      </a>
      
        

<nav class="md-nav md-nav--secondary" aria-label="目录">
  
  
  
  
    <label class="md-nav__title" for="__toc">
      <span class="md-nav__icon md-icon"></span>
      目录
    </label>
    <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
      
        <li class="md-nav__item">
  <a href="#_1" class="md-nav__link">
    <span class="md-ellipsis">
      文件上传
    </span>
  </a>
  
    <nav class="md-nav" aria-label="文件上传">
      <ul class="md-nav__list">
        
          <li class="md-nav__item">
  <a href="#_2" class="md-nav__link">
    <span class="md-ellipsis">
      一、简单上传，手动保存
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#_3" class="md-nav__link">
    <span class="md-ellipsis">
      二、 使用模型处理上传的文件
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#_4" class="md-nav__link">
    <span class="md-ellipsis">
      三、 同时上传多个文件
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#_5" class="md-nav__link">
    <span class="md-ellipsis">
      四、关于上传文件的处理器
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#_6" class="md-nav__link">
    <span class="md-ellipsis">
      五、动态修改上传处理器
    </span>
  </a>
  
</li>
        
      </ul>
    </nav>
  
</li>
      
        <li class="md-nav__item">
  <a href="#_7" class="md-nav__link">
    <span class="md-ellipsis">
      全局概念
    </span>
  </a>
  
</li>
      
        <li class="md-nav__item">
  <a href="#file" class="md-nav__link">
    <span class="md-ellipsis">
      File 对象
    </span>
  </a>
  
    <nav class="md-nav" aria-label="File 对象">
      <ul class="md-nav__list">
        
          <li class="md-nav__item">
  <a href="#file_1" class="md-nav__link">
    <span class="md-ellipsis">
      File 类
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#contentfile" class="md-nav__link">
    <span class="md-ellipsis">
      ContentFile类
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#imagefile" class="md-nav__link">
    <span class="md-ellipsis">
      ImageFile 类
    </span>
  </a>
  
</li>
        
      </ul>
    </nav>
  
</li>
      
        <li class="md-nav__item">
  <a href="#file-storage" class="md-nav__link">
    <span class="md-ellipsis">
      File storage 类
    </span>
  </a>
  
    <nav class="md-nav" aria-label="File storage 类">
      <ul class="md-nav__list">
        
          <li class="md-nav__item">
  <a href="#_8" class="md-nav__link">
    <span class="md-ellipsis">
      获取当前存储类
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#storage" class="md-nav__link">
    <span class="md-ellipsis">
      Storage类
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#filesystemstorage" class="md-nav__link">
    <span class="md-ellipsis">
      FileSystemStorage 类
    </span>
  </a>
  
</li>
        
      </ul>
    </nav>
  
</li>
      
        <li class="md-nav__item">
  <a href="#storage_1" class="md-nav__link">
    <span class="md-ellipsis">
      自定义Storage类
    </span>
  </a>
  
</li>
      
        <li class="md-nav__item">
  <a href="#csv" class="md-nav__link">
    <span class="md-ellipsis">
      生成CSV文件
    </span>
  </a>
  
    <nav class="md-nav" aria-label="生成CSV文件">
      <ul class="md-nav__list">
        
          <li class="md-nav__item">
  <a href="#pythoncsv" class="md-nav__link">
    <span class="md-ellipsis">
      一、使用Python的CSV库
    </span>
  </a>
  
</li>
        
      </ul>
    </nav>
  
</li>
      
        <li class="md-nav__item">
  <a href="#pdf" class="md-nav__link">
    <span class="md-ellipsis">
      生成PDF文件
    </span>
  </a>
  
    <nav class="md-nav" aria-label="生成PDF文件">
      <ul class="md-nav__list">
        
          <li class="md-nav__item">
  <a href="#reportlab" class="md-nav__link">
    <span class="md-ellipsis">
      一、安装ReportLab
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#_9" class="md-nav__link">
    <span class="md-ellipsis">
      二、编写视图
    </span>
  </a>
  
</li>
        
      </ul>
    </nav>
  
</li>
      
    </ul>
  
</nav>
      
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="cron.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    定时任务
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="translator.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    流媒体
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="linux.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    部署笔记
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="codeplan.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    计划表项目搭建
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="fontend.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    前后端分离架构
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="question.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    常见问题
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="exe.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    应用程序
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="mkdocs.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    mkdocs笔记
  </span>
  

      </a>
    </li>
  

    
      
      
  
  
  
  
    <li class="md-nav__item">
      <a href="markdown.html" class="md-nav__link">
        
  
  <span class="md-ellipsis">
    Markdown格式
  </span>
  

      </a>
    </li>
  

    
  </ul>
</nav>
                  </div>
                </div>
              </div>
            
            
              
              <div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
                <div class="md-sidebar__scrollwrap">
                  <div class="md-sidebar__inner">
                    

<nav class="md-nav md-nav--secondary" aria-label="目录">
  
  
  
  
    <label class="md-nav__title" for="__toc">
      <span class="md-nav__icon md-icon"></span>
      目录
    </label>
    <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
      
        <li class="md-nav__item">
  <a href="#_1" class="md-nav__link">
    <span class="md-ellipsis">
      文件上传
    </span>
  </a>
  
    <nav class="md-nav" aria-label="文件上传">
      <ul class="md-nav__list">
        
          <li class="md-nav__item">
  <a href="#_2" class="md-nav__link">
    <span class="md-ellipsis">
      一、简单上传，手动保存
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#_3" class="md-nav__link">
    <span class="md-ellipsis">
      二、 使用模型处理上传的文件
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#_4" class="md-nav__link">
    <span class="md-ellipsis">
      三、 同时上传多个文件
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#_5" class="md-nav__link">
    <span class="md-ellipsis">
      四、关于上传文件的处理器
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#_6" class="md-nav__link">
    <span class="md-ellipsis">
      五、动态修改上传处理器
    </span>
  </a>
  
</li>
        
      </ul>
    </nav>
  
</li>
      
        <li class="md-nav__item">
  <a href="#_7" class="md-nav__link">
    <span class="md-ellipsis">
      全局概念
    </span>
  </a>
  
</li>
      
        <li class="md-nav__item">
  <a href="#file" class="md-nav__link">
    <span class="md-ellipsis">
      File 对象
    </span>
  </a>
  
    <nav class="md-nav" aria-label="File 对象">
      <ul class="md-nav__list">
        
          <li class="md-nav__item">
  <a href="#file_1" class="md-nav__link">
    <span class="md-ellipsis">
      File 类
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#contentfile" class="md-nav__link">
    <span class="md-ellipsis">
      ContentFile类
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#imagefile" class="md-nav__link">
    <span class="md-ellipsis">
      ImageFile 类
    </span>
  </a>
  
</li>
        
      </ul>
    </nav>
  
</li>
      
        <li class="md-nav__item">
  <a href="#file-storage" class="md-nav__link">
    <span class="md-ellipsis">
      File storage 类
    </span>
  </a>
  
    <nav class="md-nav" aria-label="File storage 类">
      <ul class="md-nav__list">
        
          <li class="md-nav__item">
  <a href="#_8" class="md-nav__link">
    <span class="md-ellipsis">
      获取当前存储类
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#storage" class="md-nav__link">
    <span class="md-ellipsis">
      Storage类
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#filesystemstorage" class="md-nav__link">
    <span class="md-ellipsis">
      FileSystemStorage 类
    </span>
  </a>
  
</li>
        
      </ul>
    </nav>
  
</li>
      
        <li class="md-nav__item">
  <a href="#storage_1" class="md-nav__link">
    <span class="md-ellipsis">
      自定义Storage类
    </span>
  </a>
  
</li>
      
        <li class="md-nav__item">
  <a href="#csv" class="md-nav__link">
    <span class="md-ellipsis">
      生成CSV文件
    </span>
  </a>
  
    <nav class="md-nav" aria-label="生成CSV文件">
      <ul class="md-nav__list">
        
          <li class="md-nav__item">
  <a href="#pythoncsv" class="md-nav__link">
    <span class="md-ellipsis">
      一、使用Python的CSV库
    </span>
  </a>
  
</li>
        
      </ul>
    </nav>
  
</li>
      
        <li class="md-nav__item">
  <a href="#pdf" class="md-nav__link">
    <span class="md-ellipsis">
      生成PDF文件
    </span>
  </a>
  
    <nav class="md-nav" aria-label="生成PDF文件">
      <ul class="md-nav__list">
        
          <li class="md-nav__item">
  <a href="#reportlab" class="md-nav__link">
    <span class="md-ellipsis">
      一、安装ReportLab
    </span>
  </a>
  
</li>
        
          <li class="md-nav__item">
  <a href="#_9" class="md-nav__link">
    <span class="md-ellipsis">
      二、编写视图
    </span>
  </a>
  
</li>
        
      </ul>
    </nav>
  
</li>
      
    </ul>
  
</nav>
                  </div>
                </div>
              </div>
            
          
          
            <div class="md-content" data-md-component="content">
              <article class="md-content__inner md-typeset">
                
                  

  
  


<h2 id="_1">文件上传</h2>
<p>Django在处理文件上传时，文件数据会被打包封装在<code>request.FILES</code>中。</p>
<h3 id="_2">一、简单上传，手动保存</h3>
<p>首先，写一个form模型，它必须包含一个<code>FileField</code>：</p>
<p>from django import forms</p>
<p>class UploadFileForm(forms.Form):
    title = forms.CharField(max_length=50)
    file = forms.FileField()</p>
<p>处理这个表单的视图将在<code>request.FILES</code>中收到文件数据，可以用<code>request.FILES['file']</code>来获取上传文件的具体数据，其中的键值<code>'file'</code>是根据<code>file = forms.FileField()</code>的变量名来的。</p>
<p>注意：<code>request.FILES</code>只有在请求方法为POST,并且提交请求的<code>&lt;form&gt;</code>具有<code>enctype="multipart/form-data"</code>属性时才有效。 否则，<code>request.FILES</code>将为空。</p>
<p>下面是一个接收上传文件的视图范例：</p>
<p># views.py</p>
<p>from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import UploadFileForm</p>
<p># 另外写一个处理上传过来的文件的方法，并在这里导入
from somewhere import handle_uploaded_file</p>
<p>def upload_file(request):
    if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            handle_uploaded_file(request.FILES['file'])
            return HttpResponseRedirect('/success/url/')
    else:
        form = UploadFileForm()
    return render(request, 'upload.html', {'form': form})  # 思考一下这个return语句是否可以缩进到else语句中呢？</p>
<p>请注意，必须将<code>request.FILES</code>传递到form的构造函数中。</p>
<p>form = UploadFileForm(request.POST, request.FILES)</p>
<p>下面是一个处理上传文件的方法的参考例子：</p>
<p>def handle_uploaded_file(f):
    with open('some/file/name.txt', 'wb+') as destination:
        for chunk in f.chunks():
            destination.write(chunk)</p>
<p>遍历<code>UploadedFile.chunks()</code>，而不是直接使用<code>read()</code>方法，能确保大文件不会占用系统过多的内存。</p>
<h3 id="_3">二、 使用模型处理上传的文件</h3>
<p>如果是通过模型层的model来指定上传文件的保存方式的话，使用ModelForm更方便。 调用<code>form.save()</code>的时候，文件对象会保存在相应的<code>FileField</code>的<code>upload_to</code>参数指定的地方。</p>
<p>from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import ModelFormWithFileField</p>
<p>def upload_file(request):
    if request.method == 'POST':
        form = ModelFormWithFileField(request.POST, request.FILES)
        if form.is_valid():
            # 这么做就可以了，文件会被保存到Model中upload_to参数指定的位置
            form.save()
            return HttpResponseRedirect('/success/url/')
    else:
        form = ModelFormWithFileField()
    return render(request, 'upload.html', {'form': form})</p>
<p>如果手动构造一个对象，还可以简单地把文件对象直接从<code>request.FILES</code>赋值给模型：</p>
<p>from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import UploadFileForm
from .models import ModelWithFileField</p>
<p>def upload_file(request):
    if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            instance = ModelWithFileField(file_field=request.FILES['file'])
            instance.save()
            return HttpResponseRedirect('/success/url/')
    else:
        form = UploadFileForm()
    return render(request, 'upload.html', {'form': form})</p>
<h3 id="_4">三、 同时上传多个文件</h3>
<p>如果要使用一个表单字段同时上传多个文件，需要设置字段HTML标签的multiple属性为True，如下所示：</p>
<p># forms.py</p>
<p>from django import forms</p>
<p>class FileFieldForm(forms.Form):
    file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))</p>
<p>然后，自己编写一个<code>FormView</code>的子类，并覆盖它的post方法，来处理多个文件上传：</p>
<p># views.py
from django.views.generic.edit import FormView
from .forms import FileFieldForm</p>
<p>class FileFieldView(FormView):
    form_class = FileFieldForm
    template_name = 'upload.html'  # 用你的模版名替换.
    success_url = '...'  # 用你的URL或者reverse()替换.</p>
<div class="language-text highlight"><pre><span></span><code>def post(self, request, *args, **kwargs):
    form_class = self.get\_form\_class()
    form = self.get_form(form_class)
    files = request.FILES.getlist(&#39;file_field&#39;)
    if form.is_valid():
        for f in files:
            ...  \# 对每个文件做处理
        return self.form_valid(form)
    else:
        return self.form_invalid(form)
</code></pre></div>
<h3 id="_5">四、关于上传文件的处理器</h3>
<p>当用户上传一个文件的时候，Django会把文件数据传递给上传文件处理器。</p>
<p>上传处理器的配置定义在<code>FILE_UPLOAD_HANDLERS</code>中，默认为：</p>
<p>["django.core.files.uploadhandler.MemoryFileUploadHandler", "django.core.files.uploadhandler.TemporaryFileUploadHandler"]</p>
<p><code>MemoryFileUploadHandler</code>和<code>TemporaryFileUploadHandler</code>定义了Django的默认文件上传行为：将小文件读取到内存中，大文件放置在磁盘中。</p>
<p>你可以编写自己的 handlers 来自定义如何处理文件。比如，你可以使用自定义强制处理用户层面的配额，动态压缩数据，渲染进度条，甚至可以将数据发送到其他存储地址而不是本地。</p>
<p>在你保存上传文件之前，数据需要储存在某个地方。通常，如果上传文件小于2.5MB，Django会把整个内容存到内存。 这意味着，文件的保存仅仅涉及到内存中的读取和磁盘的写入，所以非常快。</p>
<p>但是，如果上传的文件很大，Django会把它写入一个临时文件，储存在你的系统临时目录中。在类Unix的平台下，Django会生成一个文件，名称类似于<code>/tmp/tmpzfp6I6.upload</code>。</p>
<h3 id="_6">五、动态修改上传处理器</h3>
<p>有时候某些视图需要不同的上传行为。也就是说，在视图中动态修改处理器列表，即<code>request.upload_handlers</code></p>
<p>比如，假设你正在编写 <code>ProgressBarUploadHandler</code> ，用来提供上传过程中的反馈。你需要添加这个处理程序到你的上传处理模块：</p>
<p>request.upload_handlers.insert(0, ProgressBarUploadHandler(request))</p>
<p>在这里使用 <code>list.insert()</code> （而不是 <code>append()</code> ），因为进度条处理程序需要在其他处理程序之前使用。</p>
<p><strong>记住，列表中的上传处理程序是按顺序处理的。</strong></p>
<p>如果你想完全替换掉先前的上传处理程序，只需要指定新列表：</p>
<p>request.upload_handlers = [ProgressBarUploadHandler(request)]</p>
<p><strong>你只能在访问 <code>request.POST</code> 或 <code>request.FILES</code> 之前修改上传处理程序</strong>。开始上传动作后修改上传处理程序没有意义，并且Django 会报错。</p>
<p>而且，默认的， <code>CsrfViewMiddleware</code>中间件会访问<code>request.POST</code>。这意味着你需要在视图上使用 <code>csrf_exempt()</code> 来允许你改变上传处理程序。然后你需要在实际处理请求的函数上使用 <code>csrf_protect()</code> 。注意这可能会让处理程序在 CSRF 检测完成之前开始接受文件上传。如下所示：</p>
<p>from django.views.decorators.csrf import csrf_exempt, csrf_protect</p>
<p>@csrf_exempt
def upload_file_view(request):
    request.upload_handlers.insert(0, ProgressBarUploadHandler(request))
    return _upload_file_view(request)</p>
<p>@csrf_protect
def _upload_file_view(request):
    ... # Process request</p>
<h2 id="_7">全局概念</h2>
<p>在详细介绍Django对文件进行处理的功能之前，我们要了解一些它的基本概念、组织方式、使用套路、主要的类和继承关系。</p>
<p>如果你不了解这些，那么复杂的源码、交错的官方文档会让你陷入泥坑。不知道怎么用？什么时候用？用什么？为什么这么用？整个一团乱！</p>
<p>这些代码都位于<code>django.core.files</code>模块中，它们主要包括：</p>
<ul>
<li>File的概念：Django对Python文件的封装。既可以用于文件上传过程中的处理，也可以单独使用</li>
<li><code>File</code>类：Django实现File的基类</li>
<li><code>ContentFile</code>类：继承了File类，不同之处是它处理的是字符串</li>
<li><code>ImageFile</code> 类：继承了File类，添加了图像的宽度和长度像素值</li>
<li><code>File</code>类的其它子类：实际上Django为<code>File</code>类还编写了一系列<code>Upload...</code>子类，只是使用较少。</li>
<li>File storage的概念：将Django的File对象保存到存储系统的API库，也就是Django如何将数据保存到硬盘中的。</li>
<li><code>settings.DEFAULT_FILE_STORAGE</code>:一个Django配置项，用来指定默认的文件存储类。默认值为<code>'django.core.files.storage.FileSystemStorage'</code>，在<code>globa_settings</code>中。</li>
<li><code>get_storage_class()</code>方法：Django提供的一个函数，通过字符串反射的方式获取指定的存储类或者<code>DEFAULT_FILE_STORAGE</code>设置的存储类</li>
<li><code>DefaultStorage</code>类:对<code>get_storage_class()</code>方法返回的对象类的进一步封装</li>
<li><code>default_storage</code>:<code>DefaultStorage</code>类的实例</li>
<li><code>Storage</code>类：Django源码中所有存储类的基类，提供通用的接口API</li>
<li><code>FileSystemStorage</code>：继承了<code>Storage</code>类，是Django原生实现的最重要、最常用、最普通的存储类。我们绝大多数时间实际使用的就是它！</li>
</ul>
<h2 id="file">File 对象</h2>
<p>Django设计了自己的文件对象。要记住，Django的File对象可以脱离本章的文件上传概念，独立使用！</p>
<h3 id="file_1">File 类</h3>
<p>File 类是围绕Python原生file对象的轻度包装，添加了一些Django特有的东西。Django在内部使用File类的实例来表示文件对象。</p>
<p>每个File对象都包含下面的属性和方法：</p>
<ul>
<li>name：文件名。包括<code>MEDIA_ROOT</code>定义的相对路径部分。</li>
<li>size：文件的尺寸，字节单位。</li>
<li>file：注意，这是File对象的file属性，不要搞混淆了！它表示File类封装的底层文件对象（Python文件对象）。</li>
<li>mode：文件的读/写模式</li>
<li>
<p>open(mode=None)：打开或者重新打开文件。mode参数和Python内置的open方法的参数一样。可以使用上下文管理器<code>with file.open() as f:</code></p>
</li>
<li>
<p><code>__iter__()</code>：遍历文件一次生成一行。</p>
</li>
<li>
<p>chunks(chunk_size=None)：遍历文件，分割成指定大小的“块”。<code>chunk_size</code> 默认为64 KB。这对于非常大的文件特别有用，因为它允许从磁盘流式传输，避免将整个文件存储在内存中。</p>
</li>
<li>
<p>multiple_chunks(chunk_size=None)：以指定的<code>chunk_size</code>进行测试，如果文件大到需要分割成多个数据块进行访问，则返回<code>True</code>，否则返回False。</p>
</li>
<li>
<p>close()：关闭文件</p>
</li>
</ul>
<p>除以上属性和方法之外，还有下面的方法：</p>
<ul>
<li><code>encoding</code></li>
<li><code>fileno</code></li>
<li><code>flush</code></li>
<li><code>isatty</code></li>
<li><code>newlines</code></li>
<li><code>read</code></li>
<li><code>readinto</code></li>
<li><code>readline</code></li>
<li><code>readlines</code></li>
<li><code>seek</code></li>
<li><code>tell</code></li>
<li><code>truncate</code></li>
<li><code>write</code></li>
<li><code>writelines</code>,</li>
<li><code>readable()</code></li>
<li><code>writable()</code></li>
<li><code>seekable()</code></li>
</ul>
<p>望文生义，它们都和Python原生的文件操作方法类似。</p>
<p>如果你想创建一个 <code>File</code> 实例，最简单的方法是使用 Python 内置的 <code>file</code> 对象：</p>
<blockquote>
<blockquote>
<blockquote>
<p>from django.core.files import File</p>
</blockquote>
</blockquote>
</blockquote>
<p># 使用Python原生的open()方法</p>
<blockquote>
<blockquote>
<blockquote>
<p>f = open('/path/to/hello.world', 'w')
myfile = File(f)</p>
</blockquote>
</blockquote>
</blockquote>
<p>注意在这里创建的文件不会自动关闭。下面的方式可以用来自动关闭文件：</p>
<blockquote>
<blockquote>
<blockquote>
<p>from django.core.files import File</p>
</blockquote>
</blockquote>
</blockquote>
<p># Create a Python file object using open() and the with statement</p>
<blockquote>
<blockquote>
<blockquote>
<p>with open('/path/to/hello.world', 'w') as f:
...     myfile = File(f)
...     myfile.write('Hello World')
...
myfile.closed
True
f.closed
True</p>
</blockquote>
</blockquote>
</blockquote>
<p>如果文件在访问后没有关闭，可能会出现文件描述符溢出的风险。</p>
<p>OSError: [Errno 24] Too many open files</p>
<h3 id="contentfile">ContentFile类</h3>
<p><code>ContentFile</code>类直接继承了<code>File</code>类，但是前者操作的是字符串或者字节内容，而不是确切的文件。例如：</p>
<p>from django.core.files.base import ContentFile</p>
<p>f1 = ContentFile("esta frase está en español")
f2 = ContentFile(b"these are bytes")</p>
<h3 id="imagefile">ImageFile 类</h3>
<p>Django为图片特别提供了一个内置类，也就是<code>django.core.files.images.ImageFile</code>，它也继承了File类。只是额外增加了两个属性：</p>
<ul>
<li>width： 图片的像素宽度</li>
<li>height：图片的像素高度</li>
</ul>
<p>比如下面的模型，使用 <code>ImageField</code> 来存储照片：</p>
<p>from django.db import models</p>
<p>class Car(models.Model):
    name = models.CharField(max_length=255)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    photo = models.ImageField(upload_to='cars')</p>
<p>所有的 Car 实例都拥有一个 photo 属性，你可以使用它来获取照片的详细信息：</p>
<blockquote>
<blockquote>
<blockquote>
<p>car = Car.objects.get(name="57 Chevy")
car.photo
<ImageFieldFile: cars/chevy.jpg>
car.photo.name
'cars/chevy.jpg'
car.photo.path      # 图片在文件系统中的路径
'/media/cars/chevy.jpg'
car.photo.url   # 访问图片的url
'http://media.example.com/cars/chevy.jpg'</p>
</blockquote>
</blockquote>
</blockquote>
<p><code>car.photo</code> 其实是一个 <code>File</code> 对象，这意味着它拥有下面所描述的所有方法和属性。</p>
<p>可以通过将文件名设置为相对于文件存储位置的路径来更改文件名（如果你正在使用默认的 FileSystemStorage ，则为 MEDIA_ROOT ）。</p>
<blockquote>
<blockquote>
<blockquote>
<p>import os
from django.conf import settings
initial_path = car.photo.path
car.photo.name = 'cars/chevy_ii.jpg'
new_path = settings.MEDIA_ROOT + car.photo.name
# Move the file on the filesystem
os.rename(initial_path, new_path)
car.save()
car.photo.path
'/media/cars/chevy_ii.jpg'
car.photo.path == new_path
True</p>
</blockquote>
</blockquote>
</blockquote>
<p>更多的 <code>ImageField</code> 使用例子：</p>
<blockquote>
<blockquote>
<blockquote>
<p>from PIL import Image
car = Car.objects.get(name='57 Chevy')
car.photo.width
191
car.photo.height
287
image = Image.open(car.photo)
# 抛出ValueError异常。因为你在尝试打开已经关闭的文件</p>
<p>car.photo.open()  # 打开文件
<ImageFieldFile: cars/chevy.jpg>
image = Image.open(car.photo)  # 再次创建Image实例
image
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=191x287 at 0x7F99A94E9048></p>
</blockquote>
</blockquote>
</blockquote>
<p>另外，此时这个File对象会有两个附加的方法save和delete：</p>
<ul>
<li>File.save（name，content，save = True）</li>
</ul>
<p>使用提供的文件名和内容保存一个新的文件。这不会替换现有文件，但会创建一个新文件并更新该对象以指向该文件（也就是说保留外面那层用来封装的皮，把内部实际的文件内容替换掉）。如果save=True，将立刻执行模型的save方法。</p>
<blockquote>
<blockquote>
<blockquote>
<p>car.photo.save('myphoto.jpg', content, save=False)
car.save()
# 等同于
car.photo.save('myphoto.jpg', content, save=True)</p>
</blockquote>
</blockquote>
</blockquote>
<ul>
<li>File.delete（save = True）</li>
</ul>
<p>从模型实例中删除文件。如果save=True，删除文件后将立刻执行模型的save方法。</p>
<h2 id="file-storage">File storage 类</h2>
<h3 id="_8">获取当前存储类</h3>
<p>在本章的一开始，我们实现了一个简单的文件上传例子。用户从浏览器通过POST发送过来文件数据，Django通过<code>request.FILES</code>拿到数据，然后我们简单粗暴地使用Python语言原生的文件操作API将数据保存到了文件系统中，通常也就是硬盘中。</p>
<p>Django为了方便我们，提供了存储类，用来帮助我们将数据保存到存储器中，不需要手动调用open方法。</p>
<p>你在模型中可能看到过这样的写法：</p>
<p>file=models.FileField(storage='xxx',......)</p>
<p>其中的storage参数就是我们要指定的存储器类。如果你不指定这个参数，Django就会使用settings中配置的默认存储类进行处理。</p>
<p>所以，我们首先要知道<code>DEFAULT_FILE_STORAGE</code>配置项，它指定Django默认的存储类，默认值为<code>'django.core.files.storage.FileSystemStorage'</code>。一般情况下，我们无感静默使用，什么都不用做。</p>
<p>但是，Django总是千方百计为我们开后门，提供钩子。</p>
<p>Django额外又为我们提供了三种获取存储类的简便方法，用于在代码中动态修改要使用的存储类：</p>
<ul>
<li><code>get_storage_class</code>(import_path=None)</li>
</ul>
<p>先看看它的源代码：</p>
<p>def get_storage_class(import_path=None):
    return import_string(import_path or settings.DEFAULT_FILE_STORAGE)</p>
<p>就两行！</p>
<p>它的作用是返回实现了存储API的存储类或者模块。</p>
<p>如果不提供参数，就使用<code>settings.DEFAULT_FILE_STORAGE</code>，也就是上面说的。</p>
<p>如果提供参数，Django将使用Python的字符串反射机制，获取对应的模块。</p>
<p><code>get_storage_class</code>方法可以用在任何地方，它不属于任何类，是个独立函数。</p>
<ul>
<li><code>DefaultStorage</code>类</li>
</ul>
<p>看看它的源代码：</p>
<p>class DefaultStorage(LazyObject):
    def _setup(self):
        self._wrapped = get_storage_class()()</p>
<p>三行！调用上面的<code>get_storage_class</code>方法并实例化，然后赋值给<code>_wrapped</code>，最后获得的就是<code>'django.core.files.storage.FileSystemStorage'</code>。</p>
<ul>
<li><code>default_storage</code>变量</li>
</ul>
<p>源代码如下：</p>
<p>default_storage = DefaultStorage()</p>
<p>根本就是<code>DefaultStorage</code>的一个实例。所以，<code>from django.core.files.storage import default_storage</code>其实就是获得了一个<code>FileSystemStorage</code>对象。</p>
<p>看下面的例子：</p>
<blockquote>
<blockquote>
<blockquote>
<p>from django.core.files.base import ContentFile
from django.core.files.storage import default_storage</p>
</blockquote>
</blockquote>
</blockquote>
<p># 注意，这个save方法是有返回值的！返回值是文件在存储系统中的路径。可以通过这个路径再去查找文件。</p>
<blockquote>
<blockquote>
<blockquote>
<p>path = default_storage.save('path/to/file', ContentFile(b'new content'))
path
'path/to/file'</p>
<p>default_storage.size(path)
11
default_storage.open(path).read()
b'new content'</p>
<p>default_storage.delete(path)
default_storage.exists(path)
False</p>
</blockquote>
</blockquote>
</blockquote>
<h3 id="storage">Storage类</h3>
<p>Storage类是Django为我们提供的存储基类，实现了一些标准的API和一些可以被子类重写的默认行为。</p>
<p>name参数：文件名</p>
<ul>
<li>
<p><code>delete</code>(<em>name</em>)：删除指定名字的文件。如果子类没有实现这个方法，会弹出<code>NotImplementedError</code> 异常。</p>
</li>
<li>
<p><code>exists</code>(<em>name</em>)： 如果文件已经存在，返回True，否则False</p>
</li>
<li>
<p><code>get_accessed_time</code>(<em>name</em>): 返回上次访问该文件的时间，以<code>datetime</code>类型。如果子类没有实现这个方法，会弹出<code>NotImplementedError</code> 异常。</p>
</li>
<li>
<p><code>get_alternative_name</code>(<em>file_root</em>, <em>file_ext</em>)：返回基于<code>file_root</code>和 <code>file_ext</code>参数的备用文件名，在扩展名之前，在文件名后附加一个下划线和一个随机的7个字符的字母数字字符串。3.0新增。</p>
</li>
<li>
<p><code>get_available_name</code>(<em>name</em>, <em>max_length=None</em>)：据<code>name</code>参数返回自由可用的文件名。文件名的长度将不超过<code>max_length</code>（如果提供）。如果找不到自由的唯一文件名，则会引发<code>SuspiciousFileOperation</code>异常 。</p>
</li>
<li>
<p><code>get_created_time</code>(<em>name</em>)： 返回文件的创建时间。如果子类没有实现这个方法，会弹出<code>NotImplementedError</code> 异常。</p>
</li>
<li>
<p><code>get_modified_time</code>(<em>name</em>)：返回上次修改该文件的时间，以<code>datetime</code>类型。如果子类没有实现这个方法，会弹出<code>NotImplementedError</code> 异常。</p>
</li>
<li>
<p><code>get_valid_name</code>(<em>name</em>)：根据<code>name</code>参数，返回一个在目标存储系统上可用的合法文件名。</p>
</li>
<li>
<p><code>generate_filename</code>(<em>filename</em>)：验证并返回一个文件名。</p>
</li>
<li>
<p><code>listdir</code>(<em>path</em>)： 列出指定path下的内容，然会一个列表的二元元组。第一个元素是目录列表，第二个元素是文件列表。如果子类没有实现这个方法，会弹出<code>NotImplementedError</code> 异常。</p>
</li>
<li>
<p><code>open</code>(<em>name</em>, <em>mode='rb'</em>)：以指定的mode打开文件</p>
</li>
<li>
<p><code>path</code>(<em>name</em>)：返回文件的路径，通过该路径可以使用Python原生的open()方法打开文件。如果子类没有实现这个方法，会弹出<code>NotImplementedError</code> 异常。</p>
</li>
<li>
<p><code>save</code>(<em>name</em>, <em>content</em>, <em>max_length=None</em>)：保存文件。如果文件名已经存在，会自动修改生成合适的文件名。content参数必须是一个<code>django.core.files.File</code>的实例，或者可以被File包装的类文件对象。</p>
</li>
<li>
<p><code>size</code>(<em>name</em>)：返回文件的大小，字节单位。如果子类没有实现这个方法，会弹出<code>NotImplementedError</code> 异常。</p>
</li>
<li>
<p><code>url</code>(<em>name</em>)：返回URL，通过该URL可以访问文件的内容。如果子类没有实现这个方法，会弹出<code>NotImplementedError</code> 异常。</p>
</li>
</ul>
<p>方法很多，不一定全要掌握，重点是下面这几个：</p>
<ul>
<li>delete</li>
<li>exists</li>
<li>listdir</li>
<li>open</li>
<li>path</li>
<li>save</li>
<li>size</li>
<li>url</li>
</ul>
<h3 id="filesystemstorage">FileSystemStorage 类</h3>
<p>实际上，我们不直接使用Storage类，而是使用FileSystemStorage 类，这也是Django实现的唯一的本地文件系统存储类。</p>
<blockquote>
<p><code>FileSystemStorage</code>（<em>location = None</em>，<em>base_url = None</em>，<em>file_permissions_mode = None</em>，<em>directory_permissions_mode = None</em>）</p>
</blockquote>
<p>FileSystemStorage类直接继承了Storage类，并提供了下面的额外属性：</p>
<ul>
<li>
<p><code>location</code>： 存放文件的目录的绝对路径。默认为<code>MEDIA_ROOT</code>设置的值。</p>
</li>
<li>
<p><code>base_url</code>： 用于访问文件的URL的基础前缀。默认为<code>MEDIA_URL</code>的值。</p>
</li>
<li>
<p><code>file_permissions_mode</code>： 文件的系统权限。默认为<code>FILE_UPLOAD_PERMISSIONS</code>配置项的值。</p>
</li>
<li>
<p><code>directory_permissions_mode</code>：目录的系统权限。默认为<code>FILE_UPLOAD_DIRECTORY_PERMISSIONS</code>配置项的值。</p>
</li>
</ul>
<p>FileSystemStorage类实现了全套的我们在Storage类中介绍过的子类必须实现的方法。</p>
<p>但是要注意， <code>FileSystemStorage.delete()</code> 方法如果删除不存在的文件，不会引发异常。</p>
<p>下面的代码将上传文件存储到 <code>/media/photos</code> ，而不是你在 <code>MEDIA_ROOT</code> 中设置的路径：</p>
<p>from django.core.files.storage import FileSystemStorage
from django.db import models</p>
<p># 自定义存储路径
fs = FileSystemStorage(location='/media/photos')</p>
<p>class Car(models.Model):
    ...
    photo = models.ImageField(storage=fs)</p>
<p>Django 3.1开始，<code>FileSystemStorage.save()</code>方法支持使用<code>pathlib.Path</code>类，并且支持回调函数形式的storage参数，如下所示：</p>
<p>from django.conf import settings
from django.db import models
from .storages import MyLocalStorage, MyRemoteStorage</p>
<p>def select_storage():
    return MyLocalStorage() if settings.DEBUG else MyRemoteStorage()</p>
<p>class MyModel(models.Model):
    my_file = models.FileField(storage=select_storage)</p>
<p>这就赋予了我们在运行过程中，动态选择存储类的能力。</p>
<h2 id="storage_1">自定义Storage类</h2>
<p>如果你需要自定义文件储存功能，比如把文件储存在远程系统中，你可以自己编写Storage类来实现这一功能。</p>
<p>实际上大多数情况下，对于本地磁盘存储，我们直接使用<code>FileSystemStorage</code>即可，对于别的需求，一般有第三方的存储类可用，在Django的生态库里查找即可。自己编写Storage类存在可靠性、可用性、安全性、性能问题，新手绕行，老手慎重。</p>
<p>但无论如何，这里还是给出基本的编写要求，以供参考：</p>
<p>第一：必须继承 <code>Django.core.files.storage.Storage</code></p>
<p>from django.core.files.storage import Storage</p>
<p>class MyStorage(Storage):
    ...</p>
<p>第二：Django 必须能以无参数的状态，实例化你的存储系统。这意味着所有的设置项都应从 <code>dango.conf.settings</code> 中获取:</p>
<p>from django.conf import settings
from django.core.files.storage import Storage</p>
<p>class MyStorage(Storage):
    def __init__(self, option=None):
        if not option:
            option = settings.CUSTOM_STORAGE_OPTIONS
        ...</p>
<p>第三：在你的存储类中，除了其他自定义的方法外，还必须实现 <code>_open()</code> 以及 <code>_save()</code> 方法。另外，如果你的类提供了本地文件存储功能，还必须重写 <code>path()</code> 方法。</p>
<p>第四：你的存储类必须是 <code>deconstructible</code>可解构的，以便在迁移中的字段上使用它时可以序列化。</p>
<p>第五：尽量实现下列方法：</p>
<ul>
<li><code>Storage.delete()</code></li>
<li><code>Storage.exists()</code></li>
<li><code>Storage.listdir()</code></li>
<li><code>Storage.size()</code></li>
<li><code>Storage.url()</code></li>
</ul>
<p>举例来说，如果列出某些存储后端的内容的代价很昂贵，那么你可以不实现 <code>Storage.listdir()</code> 方法。</p>
<p>另一个例子是只处理写入文件的后端。在这种情况下，你不需要实现上述任何方法。</p>
<hr />
<p>另外，下面是经常会用到专为自定义存储对象设计的两个钩子函数：</p>
<ul>
<li>
<p><code>_open</code>(<em>name</em>, <em>mode='rb'</em>)：真正执行打开文件功能的方法。它将被 <code>Storage.open()</code> 调用。</p>
</li>
<li>
<p><code>_save</code>(<em>name</em>, <em>content</em>)：真正执行保存功能的方法。它将被 <code>Storage.save()</code>调用。</p>
</li>
</ul>
<h1 id="csv">生成CSV文件</h1>
<p>CSV (Comma Separated Values)，以纯文本形式存储数字和文本数据的存储方式。纯文本意味着该文件是一个字符序列，不含必须像二进制数字那样的数据。CSV文件由任意数目的记录组成，记录间以某种换行符分隔；每条记录由字段组成，字段间的分隔符是其它字符或字符串，最常见的是逗号或制表符。通常，所有记录都有完全相同的字段序列。</p>
<p>CSV最常用的场景就是数据分析和机器学习中源数据的载体。</p>
<p>要在Django的视图中生成CSV文件，可以使用Python的CSV库或者Django的模板系统来实现。</p>
<h2 id="pythoncsv">一、使用Python的CSV库</h2>
<p>Python自带处理CSV文件的标准库csv。csv模块的CSV文件创建功能作用于类似于文件对象创建，并且Django的HttpResponse对象也是类似于文件的对象。</p>
<p>下面是个例子：</p>
<p>import csv
from django.http import HttpResponse</p>
<p>def some_view(request):
    # Create the HttpResponse object with the appropriate CSV header.
    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename="somefilename.csv"'</p>
<div class="language-text highlight"><pre><span></span><code>writer = csv.writer(response)
writer.writerow(\[&#39;First row&#39;, &#39;Foo&#39;, &#39;Bar&#39;, &#39;Baz&#39;\])
writer.writerow(\[&#39;Second row&#39;, &#39;A&#39;, &#39;B&#39;, &#39;C&#39;, &#39;&quot;Testing&quot;&#39;, &quot;Here&#39;s a quote&quot;\])

return response
</code></pre></div>
<p>相关说明：</p>
<ul>
<li>响应对象的MIME类型设置为<code>text/csv</code>，告诉浏览器，返回的是一个CSV文件而不是HTML文件。</li>
<li>响应对象设置了附加的<code>Content-Disposition</code>协议头，含有CSV文件的名称。文件名随便取，浏览器会在“另存为...”对话框等环境中使用它。</li>
<li>要在生成CSV的API中使用钩子非常简单：只需要把response作为第一个参数传递给<code>csv.writer</code>。<code>csv.writer</code>方法接受一个类似于文件的对象，而HttpResponse对象正好就是这么个东西。</li>
<li>对于CSV文件的每一行，调用<code>writer.writerow</code>，向它传递一个可迭代的对象比如列表或者元组。</li>
<li>CSV模板会为你处理各种引用，不用担心没有转义字符串中的引号或者逗号。只需要向writerow()传递你的原始字符串，它就会执行正确的操作。</li>
</ul>
<p>当处理大尺寸文件时，可以使用Django的<code>StreamingHttpResponse</code>类，通过流式传输，避免负载均衡器在服务器生成响应的时候断掉连接，提高传输可靠性。</p>
<p>在下面的例子中，利用Python的生成器来有效处理大尺寸CSV文件的拼接和传输：
<div class="language-text highlight"><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a>import csv
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a>
</span><span id="__span-0-3"><a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a>from django.http import StreamingHttpResponse
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a>
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a>class Echo:
</span><span id="__span-0-6"><a id="__codelineno-0-6" name="__codelineno-0-6" href="#__codelineno-0-6"></a>    &quot;&quot;&quot;An object that implements just the write method of the file-like
</span><span id="__span-0-7"><a id="__codelineno-0-7" name="__codelineno-0-7" href="#__codelineno-0-7"></a> interface.
</span><span id="__span-0-8"><a id="__codelineno-0-8" name="__codelineno-0-8" href="#__codelineno-0-8"></a> &quot;&quot;&quot;
</span><span id="__span-0-9"><a id="__codelineno-0-9" name="__codelineno-0-9" href="#__codelineno-0-9"></a>    def write(self, value):
</span><span id="__span-0-10"><a id="__codelineno-0-10" name="__codelineno-0-10" href="#__codelineno-0-10"></a>        &quot;&quot;&quot;Write the value by returning it, instead of storing in a buffer.&quot;&quot;&quot;
</span><span id="__span-0-11"><a id="__codelineno-0-11" name="__codelineno-0-11" href="#__codelineno-0-11"></a>        return value
</span><span id="__span-0-12"><a id="__codelineno-0-12" name="__codelineno-0-12" href="#__codelineno-0-12"></a>
</span><span id="__span-0-13"><a id="__codelineno-0-13" name="__codelineno-0-13" href="#__codelineno-0-13"></a>def some\_streaming\_csv_view(request):
</span><span id="__span-0-14"><a id="__codelineno-0-14" name="__codelineno-0-14" href="#__codelineno-0-14"></a>    &quot;&quot;&quot;A view that streams a large CSV file.&quot;&quot;&quot;
</span><span id="__span-0-15"><a id="__codelineno-0-15" name="__codelineno-0-15" href="#__codelineno-0-15"></a>    \# Generate a sequence of rows. The range is based on the maximum number of
</span><span id="__span-0-16"><a id="__codelineno-0-16" name="__codelineno-0-16" href="#__codelineno-0-16"></a>    \# rows that can be handled by a single sheet in most spreadsheet
</span><span id="__span-0-17"><a id="__codelineno-0-17" name="__codelineno-0-17" href="#__codelineno-0-17"></a>    \# applications.
</span><span id="__span-0-18"><a id="__codelineno-0-18" name="__codelineno-0-18" href="#__codelineno-0-18"></a>    rows = (\[&quot;Row {}&quot;.format(idx), str(idx)\] for idx in range(65536))
</span><span id="__span-0-19"><a id="__codelineno-0-19" name="__codelineno-0-19" href="#__codelineno-0-19"></a>    pseudo_buffer = Echo()
</span><span id="__span-0-20"><a id="__codelineno-0-20" name="__codelineno-0-20" href="#__codelineno-0-20"></a>    writer = csv.writer(pseudo_buffer)
</span><span id="__span-0-21"><a id="__codelineno-0-21" name="__codelineno-0-21" href="#__codelineno-0-21"></a>    response = StreamingHttpResponse((writer.writerow(row) for row in rows),
</span><span id="__span-0-22"><a id="__codelineno-0-22" name="__codelineno-0-22" href="#__codelineno-0-22"></a>                                     content_type=&quot;text/csv&quot;)
</span><span id="__span-0-23"><a id="__codelineno-0-23" name="__codelineno-0-23" href="#__codelineno-0-23"></a>    response\[&#39;Content-Disposition&#39;\] = &#39;attachment; filename=&quot;somefilename.csv&quot;&#39;
</span><span id="__span-0-24"><a id="__codelineno-0-24" name="__codelineno-0-24" href="#__codelineno-0-24"></a>    return response
</span></code></pre></div>
二、使用Django的模板系统</p>
<hr />
<p>也可以使用Django的模板系统来生成CSV。比起便捷的Python-csv库，这样做比较低级，不建议使用，这里只是展示一下有这种方式而已。</p>
<p>思路是，传递一个项目的列表给你的模板，并且让模板在for循环中输出逗号。下面是一个例子，它像上面一样生成相同的CSV文件：</p>
<p>from django.http import HttpResponse
from django.template import loader</p>
<p>def some_view(request):
    # Create the HttpResponse object with the appropriate CSV header.
    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename="somefilename.csv"'</p>
<div class="language-text highlight"><pre><span></span><code>\# The data is hard-coded here, but you could load it from a database or
\# some other source.
csv_data = (
    (&#39;First row&#39;, &#39;Foo&#39;, &#39;Bar&#39;, &#39;Baz&#39;),
    (&#39;Second row&#39;, &#39;A&#39;, &#39;B&#39;, &#39;C&#39;, &#39;&quot;Testing&quot;&#39;, &quot;Here&#39;s a quote&quot;),
)

t = loader.get_template(&#39;my\_template\_name.txt&#39;)
c = {&#39;data&#39;: csv_data}
response.write(t.render(c))
return response
</code></pre></div>
<p>然后，创建模板<code>my_template_name.txt</code>，带有以下模板代码：</p>
<p>{% for row in data %}"{{ row.0|addslashes }}", "{{ row.1|addslashes }}", "{{ row.2|addslashes }}", "{{ row.3|addslashes }}", "{{ row.4|addslashes }}"
{% endfor %}</p>
<h1 id="pdf">生成PDF文件</h1>
<p>阅读: 28974     <a href="#comments">评论</a>：3</p>
<hr />
<p>可以通过开源的Python PDF库<code>ReportLab</code>来实现PDF文件的动态生成。</p>
<h2 id="reportlab">一、安装ReportLab</h2>
<p>ReportLab库在PyPI上提供，可以使用pip来安装：</p>
<p>$ pip install reportlab</p>
<p>在Python交互解释器中导入它来测试安装：</p>
<blockquote>
<blockquote>
<blockquote>
<p>import reportlab</p>
</blockquote>
</blockquote>
</blockquote>
<p>如果没有抛出任何错误，证明已安装成功。</p>
<h2 id="_9">二、编写视图</h2>
<p>利用 Django 动态生成 PDF 的关键是 ReportLab API 作用于类文件对象，而 Django 的 <code>FileResponse</code> 对象接收类文件对象。</p>
<p>这有个 "Hello World" 示例:</p>
<p>import io
from django.http import FileResponse
from reportlab.pdfgen import canvas</p>
<p>def some_view(request):
    # Create a file-like buffer to receive PDF data.
    buffer = io.BytesIO()</p>
<div class="language-text highlight"><pre><span></span><code>\# Create the PDF object, using the buffer as its &quot;file.&quot;
p = canvas.Canvas(buffer)

\# Draw things on the PDF. Here&#39;s where the PDF generation happens.
\# See the ReportLab documentation for the full list of functionality.
p.drawString(100, 100, &quot;Hello world.&quot;)

\# Close the PDF object cleanly, and we&#39;re done.
p.showPage()
p.save()

\# FileResponse sets the Content-Disposition header so that browsers
\# present the option to save the file.
buffer.seek(0)
return FileResponse(buffer, as_attachment=True, filename=&#39;hello.pdf&#39;)
</code></pre></div>
<p>相关说明：</p>
<ul>
<li>MIME会自动设置为<code>application/pdf</code>。</li>
<li>将 <code>as_attachment=True</code> 传递给 <code>FileResponse</code> 时，表示这是一个可下载附件，它会设置合适的 <code>Content-Disposition</code> 头，告诉 Web 浏览器弹出一个对话框，提示或确认如何处理该文档，即便设备已配置默认行为。若省略了 <code>as_attachment</code> 参数，浏览器会用已配置的用于处理 PDF 的程序或插件来处理该 PDF。</li>
<li>你也可以提供可选参数 <code>filename</code>。浏览器的<code>“另存为…”</code>对话框会用到它。</li>
<li>注意，所有后续生成 PDF 的方法都是在 PDF 对象上调用的（本例中是 <code>p</code>）——而不是在 <code>buffer</code> 上调用。</li>
<li>
<p>最后，牢记在 PDF 文件上调用 <code>showPage()</code> 和 <code>save()</code>。</p>
</li>
<li>
<p>注意：ReportLab并不是线程安全的。</p>
</li>
</ul>












                
              </article>
            </div>
          
          
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
        </div>
        
      </main>
      
        <footer class="md-footer">
  
  <div class="md-footer-meta md-typeset">
    <div class="md-footer-meta__inner md-grid">
      <div class="md-copyright">
  
    <div class="md-copyright__highlight">
      Copyright 2024 Write By Heng
    </div>
  
  
</div>
      
        <div class="md-social">
  
    
    
    
    
      
      
    
    <a href="http://home.liheng.work/" target="_blank" rel="noopener" title="home.liheng.work" class="md-social__link">
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2024 Fonticons, Inc.--><path d="M244 246c-3.2-2-6.3-2.9-10.1-2.9-6.6 0-12.6 3.2-19.3 3.7l1.7 4.9zm135.9 197.9c-19 0-64.1 9.5-79.9 19.8l6.9 45.1c35.7 6.1 70.1 3.6 106-9.8-4.8-10-23.5-55.1-33-55.1zM340.8 177c6.6 2.8 11.5 9.2 22.7 22.1 2-1.4 7.5-5.2 7.5-8.6 0-4.9-11.8-13.2-13.2-23 11.2-5.7 25.2-6 37.6-8.9 68.1-16.4 116.3-52.9 146.8-116.7C548.3 29.3 554 16.1 554.6 2l-2 2.6c-28.4 50-33 63.2-81.3 100-31.9 24.4-69.2 40.2-106.6 54.6l-6.3-.3v-21.8c-19.6 1.6-19.7-14.6-31.6-23-18.7 20.6-31.6 40.8-58.9 51.1-12.7 4.8-19.6 10-25.9 21.8 34.9-16.4 91.2-13.5 98.8-10zM555.5 0l-.6 1.1-.3.9.6-.6zm-59.2 382.1c-33.9-56.9-75.3-118.4-150-115.5l-.3-6c-1.1-13.5 32.8 3.2 35.1-31l-14.4 7.2c-19.8-45.7-8.6-54.3-65.5-54.3-14.7 0-26.7 1.7-41.4 4.6 2.9 18.6 2.2 36.7-10.9 50.3l19.5 5.5c-1.7 3.2-2.9 6.3-2.9 9.8 0 21 42.8 2.9 42.8 33.6 0 18.4-36.8 60.1-54.9 60.1-8 0-53.7-50-53.4-60.1l.3-4.6 52.3-11.5c13-2.6 12.3-22.7-2.9-22.7-3.7 0-43.1 9.2-49.4 10.6-2-5.2-7.5-14.1-13.8-14.1-3.2 0-6.3 3.2-9.5 4-9.2 2.6-31 2.9-21.5 20.1L15.9 298.5c-5.5 1.1-8.9 6.3-8.9 11.8 0 6 5.5 10.9 11.5 10.9 8 0 131.3-28.4 147.4-32.2 2.6 3.2 4.6 6.3 7.8 8.6 20.1 14.4 59.8 85.9 76.4 85.9 24.1 0 58-22.4 71.3-41.9 3.2-4.3 6.9-7.5 12.4-6.9.6 13.8-31.6 34.2-33 43.7-1.4 10.2-1 35.2-.3 41.1 26.7 8.1 52-3.6 77.9-2.9 4.3-21 10.6-41.9 9.8-63.5l-.3-9.5c-1.4-34.2-10.9-38.5-34.8-58.6-1.1-1.1-2.6-2.6-3.7-4 2.2-1.4 1.1-1 4.6-1.7 88.5 0 56.3 183.6 111.5 229.9 33.1-15 72.5-27.9 103.5-47.2-29-25.6-52.6-45.7-72.7-79.9zm-196.2 46.1v27.2l11.8-3.4-2.9-23.8zm-68.7-150.4 24.1 61.2 21-13.8-31.3-50.9zm84.4 154.9 2 12.4c9-1.5 58.4-6.6 58.4-14.1 0-1.4-.6-3.2-.9-4.6-26.8 0-36.9 3.8-59.5 6.3z"/></svg>
    </a>
  
    
    
    
    
      
      
    
    <a href="https://github.com/lmliheng?tab=repositories" target="_blank" rel="noopener" title="github.com" class="md-social__link">
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!--! Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2024 Fonticons, Inc.--><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
    </a>
  
</div>
      
    </div>
  </div>
</footer>
      
    </div>
    <div class="md-dialog" data-md-component="dialog">
      <div class="md-dialog__inner md-typeset"></div>
    </div>
    
    
    <script id="__config" type="application/json">{"base": ".", "features": ["content.code.copy", "content.code.select"], "search": "assets/javascripts/workers/search.b8dbb3d2.min.js", "translations": {"clipboard.copied": "\u5df2\u590d\u5236", "clipboard.copy": "\u590d\u5236", "search.result.more.one": "\u5728\u8be5\u9875\u4e0a\u8fd8\u6709 1 \u4e2a\u7b26\u5408\u6761\u4ef6\u7684\u7ed3\u679c", "search.result.more.other": "\u5728\u8be5\u9875\u4e0a\u8fd8\u6709 # \u4e2a\u7b26\u5408\u6761\u4ef6\u7684\u7ed3\u679c", "search.result.none": "\u6ca1\u6709\u627e\u5230\u7b26\u5408\u6761\u4ef6\u7684\u7ed3\u679c", "search.result.one": "\u627e\u5230 1 \u4e2a\u7b26\u5408\u6761\u4ef6\u7684\u7ed3\u679c", "search.result.other": "# \u4e2a\u7b26\u5408\u6761\u4ef6\u7684\u7ed3\u679c", "search.result.placeholder": "\u952e\u5165\u4ee5\u5f00\u59cb\u641c\u7d22", "search.result.term.missing": "\u7f3a\u5c11", "select.version": "\u9009\u62e9\u5f53\u524d\u7248\u672c"}}</script>
    
    
      <script src="assets/javascripts/bundle.dd8806f2.min.js"></script>
      
    
  </body>
</html>